#include <iostream>
#include <string>

#include "coefficient.h"
#include "helpers.h"
#include "linear_system.h"
#include "parser.h"
#include "simple_validator.h"

using namespace std;

/** @param number_of_lines the number of equations of the system.
 *  @param number_of_variables the number of variables in the equation.
 *  @param [out] error this string is used to return an error message in \
 *  case something went wrong.
 *  @return a pointer to the linear system read through the standard input.
 */
LinearSystem* LinearSystem::ReadLinearSystemFromStdin(int number_of_lines,
                                                      int number_of_variables,
                                                      string& error) {
  LinearSystem *system = new LinearSystem();
  system->number_of_equations_ = number_of_lines;
  system->number_of_variables_ = number_of_variables;

  SimpleValidator simple_validator;
  Parser parser;
  // For each equation.
  for (int line = 0; line < number_of_lines; ++line) {
    string line;
    getline(cin, line);
    // First of all, we do some validation.
    simple_validator.set_equation(line);
    SimpleValidator::FormatCodeValue format = simple_validator.DoValidation();
    if (format != SimpleValidator::Correct) {
      error = SimpleValidator::mapping[format];
      delete system;
      return NULL; 
    }

    // We start parsing the equation.
    parser.set_equation(line);

    LinearEquation linear_equation(number_of_variables+1);

    // The one at the right should be a number.
    Coefficient result = parser.GetResult();

    linear_equation.SetIthCoefficient(number_of_variables, result);
    vector<pair<string, Coefficient> > leftside = parser.GetLeftCoefficients(); 
    // We parse each term.
    for (int term = 0; term < (int)leftside.size(); ++term) {
      if (system->variable_column.find(leftside[term].first) ==
          system->variable_column.end()) {
        system->variable_column[leftside[term].first]
           = system->variables.size();
        system->variables.push_back(leftside[term].first);
      }
      linear_equation.SetIthCoefficient(
          system->variable_column[leftside[term].first],
          linear_equation.GetIthCoefficient(
              system->variable_column[leftside[term].first])
          + leftside[term].second);
    }
    system->system.push_back(linear_equation);
  }
  return system;
}

/** @return a pointer to the solved linear system. */
LinearSystem* LinearSystem::Resolve() {
  LinearSystem *result = new LinearSystem();
  *result = *this;

  int next_row = 0;
  for (int column = 0; column < number_of_variables_; ++column) {
    // We go through all the column to get a value different from zero.
    int row = next_row;
    for (; row < number_of_equations_; ++row) {
      if (system[row].GetIthCoefficient(column).NonZeroCoefficient())
        break;
    }
    // In case we have a value different than 0, it should be a non-free var.
    if (row == number_of_equations_) continue;

    // swap the corresponding LinearEquation's.
    swap(system[next_row], system[row]);

    row = next_row;
    next_row++;

    // Normalize that row.
    Coefficient pivot = system[row].GetIthCoefficient(column);
    
    for (int col = column; col < number_of_variables_+1; ++col) {
      system[row].SetIthCoefficient(
          col, system[row].GetIthCoefficient(col)/pivot);
    }

    // Normalize the appropriate rows.
    for (int aux_row = 0; aux_row < number_of_equations_; ++aux_row) {
      if (row == aux_row) continue;
      if (system[aux_row].GetIthCoefficient(column).NonZeroCoefficient()) {
        // This row needs to be normalized.
        Coefficient aux_pivot = system[aux_row].GetIthCoefficient(column);
        for (int c = column; c < number_of_variables_+1; ++c) {
          Coefficient new_coef = system[aux_row].GetIthCoefficient(c);
          new_coef = new_coef - system[row].GetIthCoefficient(c) * aux_pivot;
          system[aux_row].SetIthCoefficient(c, new_coef);
        }
      }

    }
  }
  swap(*result, *this);
  return result;
}

void LinearSystem::PrintSystemToStdout() {
  cout << endl;
  cout << "System consisting of " << number_of_equations_ << " equations";
  cout << " and " << number_of_variables_ << " variables." << endl;
  for (int rows = 0; rows < number_of_equations_; ++rows) {
      system[rows].PrintEquationToStdout();
  }
}

void LinearSystem::PrintSolutionToStdout() {
  // First, we check if there is no solution.
  int row, col;
  for (row = 0; row < number_of_equations_; ++row) {
    for (col = 0; col < number_of_variables_; ++col) {
      if (system[row].GetIthCoefficient(col).NonZeroCoefficient()) break;
    }
    if (col == number_of_variables_ &&
        system[row].GetIthCoefficient(
            number_of_variables_).NonZeroCoefficient()) {
      cout << "No Solution" << endl;
      return;
    }
  }

  // Check the last row to see if there are many possible solutions.
  col = 0;
  int last_row = min(number_of_equations_-1, number_of_variables_-1);
  for (; col < number_of_variables_; ++col) {
    if (system[last_row].GetIthCoefficient(
            col).NonZeroCoefficient())
      break;
  }

  if (last_row < number_of_variables_-1 or col == number_of_variables_) {
    cout << "Multiple solutions." << endl;
    return;
  }

  // We print the solution.
  cout << endl;
  for (int row = 0; row < number_of_equations_; ++row) {
    cout << variables[row] << " = ";
    system[row].GetIthCoefficient(
        number_of_variables_).PrintCoefficientToStdout();
    cout << endl;
  }
}
